package com.panda.autotest.core.parse;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import com.alibaba.fastjson.JSON;
import com.panda.autotest.core.AutoTestConstants;
import com.panda.autotest.core.engine.SqliteJdbcHandler;
import com.panda.autotest.dao.CaseDao;
import com.panda.autotest.dao.CaseExtDao;
import com.panda.autotest.dao.model.Case;

public class CaseConfigManager implements Serializable, InitializingBean, ApplicationContextAware {
	private Logger logger = LoggerFactory.getLogger(CaseConfigManager.class);
	private ApplicationContext applicationContext;
	private static final long serialVersionUID = 1L;
	private Map<String, CaseConfig> caseConfigMap;
	@Value("${case.dir}")
	private String location = "classpath:case/*.xml";
	@Value("${update.eod.date.sql}")
	private String updateEODSql;
	@Value("${changeEodDay.site.url}")
	private String changeEodDaySiteUrl;
	@Value("${changeEodDay.click.site.checkbox.xpath}")
	private String changeEodDayClickSiteCheckboxXpath;
	@Value("${changeEodDay.click.update.xpath}")
	private String changeEodDayClickUpdateXpath;
	@Value("${changeEodDay.input.bizDate.xpath}")
	private String changeEodDayInputBizDateXpath;
	@Value("${changeEodDay.input.eodDate.xpath}")
	private String changeEodDayInputEodDateXpath;
	@Value("${changeEodDay.click.submit.xpath}")
	private String changeEodDayClickSubmitXpath;
	@Value("${changeEodDay.click.currentDate.xpath}")
	private String changeEodDayClickCurrentDateXpath;
	@Value("${changeEodDay.style}")
	private String changeEodDayStyle;
	// 登录
	@Value("${login.url}")
	private String loginUrl;
	@Value("${logout.url}")
	private String logoutUrl;
	@Value("${login.input.companyCode.xpath}")
	private String loginInputCompanyCodeXpath;
	@Value("${login.input.username.xpath}")
	private String loginInputUsernameXpath;
	@Value("${login.input.password.xpath}")
	private String loginInputPasswordXpath;
	@Value("${login.input.authCode.xpath}")
	private String loginInputAuthCodeXpath;
	@Value("${login.click.submit.xpath}")
	private String loginClickSubmitXpath;
	// job
	@Value("${job.url}")
	private String jobUrl;
	@Value("${job.search.jobName.xpath}")
	private String jobSearchJobNameXpath;
	@Value("${job.search.click.query.xpath}")
	private String jobSearchClickQueryXpath;
	@Value("${job.click.checkbox.xpath}")
	private String jobClickCheckboxXpath;
	@Value("${job.click.run.xpath}")
	private String jobClickRunXpath;
	@Value("${click.confirm.xpath}")
	private String clickConfirmXpath;
	@Autowired
	private SqliteJdbcHandler sqliteJdbcHandler;

	@Autowired
	private CaseExtDao caseExtDao;

	@Autowired
	private CaseDao caseDao;

	private SAXReader reader = new SAXReader();

	public Map<String, CaseConfig> getCaseConfigMap() {
		if (caseConfigMap == null) {
			caseConfigMap = new ConcurrentHashMap<String, CaseConfig>();
		}
		return caseConfigMap;
	}

	public void setCaseConfigMap(Map<String, CaseConfig> caseConfigMap) {
		this.caseConfigMap = caseConfigMap;
	}

	public String getLocation() {
		return location;
	}

	public void setLocation(String location) {
		this.location = location;
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		this.initDB();
		this.refresh(this.groupCaseByFileName(caseExtDao.selectALL()));
		caseExtDao.deleteALL();
		Set<Entry<String, CaseConfig>> entrySet = this.getCaseConfigMap().entrySet();
		for (Entry<String, CaseConfig> entry : entrySet) {
			caseDao.insert((Case) entry.getValue());
		}
	}

	/**
	 * @author chen.pian
	 */
	public void updateCacheFromDb(Case caseVo, CaseConfig config) {
		config.setErrorImgName(caseVo.getErrorImgName());
		config.setUuid(caseVo.getUuid());
		config.setErrorMsg(caseVo.getErrorMsg());
		config.setLastRunTime(caseVo.getLastRunTime());
		config.setStatus(caseVo.getStatus());
		config.setInfos(caseVo.getInfos());
		if (StringUtils.hasText(config.getInfos())) {
			config.setStepInfos(JSON.parseArray(config.getInfos(), Info.class));
		}
		config.setUseTimes(caseVo.getUseTimes());
	}

	/**
	 * @author chen.pian
	 * @param caseList
	 * @return
	 */
	public Map<String, Case> groupCaseByFileName(List<Case> caseList) {
		Map<String, Case> caseMap = new HashMap<String, Case>();
		if (caseList != null && caseList.size() >= 0) {
			for (Case caseVo : caseList) {
				caseMap.put(caseVo.getFileName(), caseVo);
			}
		}
		return caseMap;
	}

	/**
	 * @author chen.pian
	 * @param keys
	 */
	public void clearCaches(List<String> keys) {
		for (String key : keys) {
			this.clearCache(key);
		}
	}

	/**
	 * @author chen.pian
	 * @param key
	 */
	public void clearCache(String key) {
		CaseConfig caseConfig = this.getCaseConfigMap().remove(key);
		FileUtils.deleteQuietly(new File(caseConfig.getFilePath()));
	}

	/**
	 * @author chen.pian
	 * @param caseConfigs
	 */
	public void updateCaches(List<CaseConfig> caseConfigs) {
		for (CaseConfig caseConfig : caseConfigs) {
			this.updateCache(caseConfig);
		}
	}

	public void updateCache(CaseConfig caseConfig) {
		this.getCaseConfigMap().put(caseConfig.getUuid(), caseConfig);
	}

	public void initDB() throws IOException {
		Resource queryResource = applicationContext.getResource("classpath:db/query.sql");
		String querySql = IOUtils.toString(queryResource.getInputStream(), "UTF-8");
		Map<String, Object> resultMap = sqliteJdbcHandler.queryForMap(querySql);
		Object obj = resultMap.get("num");
		logger.info("#####t_case#####" + obj);
		if (obj == null || ((Integer) obj) == 0) {
			Resource initResource = applicationContext.getResource("classpath:db/init.sql");
			String initSql = IOUtils.toString(initResource.getInputStream(), "UTF-8");
			sqliteJdbcHandler.initSql(initSql);
		}
		resultMap = sqliteJdbcHandler
				.queryForMap("select count(name) num from sqlite_master where type='table' and name='t_sequence' order by name");
		obj = resultMap.get("num");
		logger.info("#####t_sequence#####" + obj);
		if (obj == null || ((Integer) obj) == 0) {
			sqliteJdbcHandler.initSql("create table t_sequence(num int)");
		}
		sqliteJdbcHandler.initSql("update t_sequence set num = 0");
	}

	public void parse(Element root, CaseConfig config) {
		Assert.notNull(root, "Root must not be null");
		Attribute attribute = root.attribute("name");
		String name = attribute.getText();
		Assert.notNull(name, "Root attribute name must not be null");
		config.setCaseName(name);
		this.parseSetting(root, config);
		this.parseRun(root, config);
	}

	public CaseConfig buildCaseConfig(InputStream inputStream, String fileName, String absolutePath, String uuid)
			throws Exception {
		Document document = reader.read(inputStream);
		Element root = document.getRootElement();
		CaseConfig config = new CaseConfig();
		config.setFileName(fileName);
		config.setFilePath(absolutePath);
		this.parse(root, config);
		if (uuid == null) {
			uuid = UUID.randomUUID().toString();
			config.setCaseOrder(sqliteJdbcHandler.nextSequence());
			config.setStatus(AutoTestConstants.SUCCESS);
		}
		if (config.getCountLogin() != 1) {
			config.setLoginCompanyCode("OTHER");
		}
		config.setUuid(uuid);
		return config;
	}

	public void refresh(Map<String, Case> caseMap) throws Exception {
		Resource[] resources = applicationContext.getResources(location);
		if (resources == null || resources.length <= 0) {
			return;
		}
		for (Resource resource : resources) {
			if (resource.getFile().isFile()) {
				CaseConfig config = this.buildCaseConfig(resource.getInputStream(), resource.getFilename(), resource
						.getFile().getAbsolutePath(), null);
				if (caseMap != null) {
					Case caseVo = caseMap.get(config.getFileName());
					if (caseVo != null) {
						this.updateCacheFromDb(caseVo, config);
					}
				}
				this.getCaseConfigMap().put(config.getUuid(), config);
			}
		}
	}

	/**
	 * 解析run内容
	 * 
	 * @author chen.pian
	 * @param root
	 * @param config
	 */
	private void parseRun(Element root, CaseConfig config) {
		Element run = root.element("run");
		this.traverse(run, config);
	}

	@SuppressWarnings("all")
	public void traverse(Element ele, CaseConfig config) {
		Step step = null;
		String nodeName = ele.getName();
		if ("step".equals(nodeName)) {
			Type type = Type.valueOf(ele.attributeValue("type"));
			if (Type.changeEodDate == type) {
				this.changeEodDate(ele, config);
			} else if (Type.login == type) {
				this.login(ele, config);
			} else if (Type.job == type) {
				this.job(ele, config);
			} else {
				step = new Step();
				step.setType(type);
				step.setDesc(ele.attributeValue("desc"));
				Element xpath = ele.element("xpath");
				if (xpath != null) {
					step.setXpath(xpath.getText());
				}
				Element url = ele.element("url");
				if (url != null) {
					step.setUrl(url.getText());
				}
				Element value = ele.element("value");
				if (value != null) {
					step.setValue(value.getText());
				}
				if (Type.url == type) {
					if (step.getUrl() == null) {
						step.setUrl(step.getValue());
					}
				}
				Element schema = ele.element("schema");
				if (schema != null) {
					step.setSchema(schema.getText());
				}
				if (ele.attributeValue("afterWait") != null) {
					step.setAfterWait(Boolean.valueOf(ele.attributeValue("afterWait")));
				}
				this.parsePropertys(ele, step);
			}
		}
		if (step != null) {
			config.getStepList().add(step);
		}
		Iterator<Element> it = ele.elementIterator();
		while (it.hasNext()) {
			traverse(it.next(), config);
		}
	}

	/**
	 * 
	 * @author chen.pian
	 * @param ele
	 * @param step
	 */
	@SuppressWarnings("all")
	private void parsePropertys(Element ele, Step step) {
		List<Element> propertys = ele.selectNodes("propertys/property");
		if (propertys != null && propertys.size() > 0) {
			for (Element property : propertys) {
				step.getParamMap().put(property.attributeValue("name"), property.attributeValue("value"));
			}
		}
	}

	/**
	 * @author chen.pian
	 * @param ele
	 * @param config
	 */
	private void job(Element ele, CaseConfig config) {
		String value = ele.elementText("value");
		Step step = null;
		step = new Step();
		step.setType(Type.url);
		step.setDesc("定时任务手动触发");
		step.setUrl(jobUrl);
		config.getStepList().add(step);
		// 定时任务筛选 任务名称
		step = new Step();
		step.setType(Type.input);
		step.setDesc("任务名称筛选");
		step.setXpath(jobSearchJobNameXpath);
		step.setValue(value);
		config.getStepList().add(step);
		// 点击查询
		step = new Step();
		step.setType(Type.click);
		step.setDesc("查询");
		step.setXpath(jobSearchClickQueryXpath);
		step.setAfterWait(true);
		config.getStepList().add(step);
		// 点击checkbox
		step = new Step();
		step.setType(Type.click);
		step.setDesc("定时任务列表第一条");
		step.setXpath(jobClickCheckboxXpath);
		config.getStepList().add(step);
		// 点击执行
		step = new Step();
		step.setType(Type.click);
		step.setDesc("执行");
		step.setXpath(jobClickRunXpath);
		config.getStepList().add(step);
		// 点击确定
		step = new Step();
		step.setType(Type.submit);
		step.setDesc("确定");
		step.setXpath(clickConfirmXpath);
		config.getStepList().add(step);
		// 等待执行
		step = new Step();
		step.setType(Type.queryWaitsql);
		step.setSchema("cms");
		this.parsePropertys(ele, step);
		step.setSql("select RUNFLAG from TBL_JOB where JOBCODE='${jobcode}'");
		step.setCondition("RUNFLAG == 'N'");
		step.setDesc("job=" + value + "执行完毕");
		config.getStepList().add(step);

	}

	/**
	 * @author chen.pian
	 * @param ele
	 * @param config
	 */
	public void login(Element ele, CaseConfig config) {
		Step step = null;
		String companyCode = ele.attributeValue("companyCode");
		String username = ele.attributeValue("username");
		String password = ele.attributeValue("password");
		String authCode = ele.attributeValue("authCode");
		config.setLoginCompanyCode(companyCode);
		config.setCountLogin(config.getCountLogin() + 1);
		step = new Step();
		step.setType(Type.url);
		step.setDesc("注销");
		step.setUrl(logoutUrl);
		step.setLogin(true);
		config.getStepList().add(step);

		step = new Step();
		step.setType(Type.url);
		step.setDesc("登录");
		step.setUrl(loginUrl);
		step.setLogin(true);
		config.getStepList().add(step);

		step = new Step();
		step.setType(Type.input);
		step.setDesc("公司");
		step.setXpath(loginInputCompanyCodeXpath);
		step.setValue(companyCode);
		config.getStepList().add(step);

		step = new Step();
		step.setType(Type.input);
		step.setDesc("用户名");
		step.setXpath(loginInputUsernameXpath);
		step.setValue(username);
		config.getStepList().add(step);

		step = new Step();
		step.setType(Type.input);
		step.setDesc("密码");
		step.setXpath(loginInputPasswordXpath);
		step.setValue(password);
		config.getStepList().add(step);

		step = new Step();
		step.setType(Type.input);
		step.setDesc("验证码");
		step.setXpath(loginInputAuthCodeXpath);
		step.setValue(authCode);
		config.getStepList().add(step);

		step = new Step();
		step.setType(Type.click);
		step.setDesc("提交");
		step.setXpath(loginClickSubmitXpath);
		config.getStepList().add(step);

		step = new Step();
		step.setType(Type.url);
		step.setDesc("进入初始化页面");
		step.setUrl(config.getSettingMap().get("startUrl"));
		step.setLogin(true);
		config.getStepList().add(step);
	}

	/**
	 * @author chen.pian
	 * @param ele
	 * @param config
	 */
	public void changeEodDate(Element ele, CaseConfig config) {
		Step step = null;
		Element value = ele.element("value");
		if ("P".equals(changeEodDayStyle)) {
			// 输入机构列表的url
			step = new Step();
			step.setType(Type.url);
			step.setDesc("机构");
			step.setUrl(changeEodDaySiteUrl);
			config.getStepList().add(step);
			// 点击机构列表的第一条记录
			step = new Step();
			step.setType(Type.click);
			step.setDesc("机构第一条记录");
			step.setXpath(changeEodDayClickSiteCheckboxXpath);
			config.getStepList().add(step);
			// 点击机构列表的更新按钮
			step = new Step();
			step.setType(Type.click);
			step.setDesc("机构列表的更新按钮");
			step.setXpath(changeEodDayClickUpdateXpath);
			config.getStepList().add(step);
			// 更新bizDate和EodDate
			step = new Step();
			step.setType(Type.date);
			step.setDesc("更新bizDate");
			step.setXpath(changeEodDayInputBizDateXpath);
			step.setValue(value.getText());
			config.getStepList().add(step);
			// 更新bizDate和EodDate
			step = new Step();
			step.setType(Type.date);
			step.setDesc("更新eodDate");
			step.setXpath(changeEodDayInputEodDateXpath);
			step.setValue(value.getText());
			config.getStepList().add(step);
			// 点击提交按钮
			step = new Step();
			step.setType(Type.submit);
			step.setDesc("提交");
			step.setXpath(changeEodDayClickSubmitXpath);
			config.getStepList().add(step);
			step = new Step();
			step.setType(Type.validateEod);
			step.setValue(value.getText());
			step.setDesc("");
			step.setXpath(changeEodDayClickCurrentDateXpath);
			config.getStepList().add(step);
		} else {
			step = new Step();
			String date = ele.elementText("value");
			step.setType(Type.updateSql);
			step.setSchema("cms");
			step.getParamMap().put("DATE", date);
			step.getParamMap().put("SITECODE", config.getLoginCompanyCode());
			this.parsePropertys(ele, step);
			step.setSql(updateEODSql.replaceAll("@", "\\$"));
			step.setDesc("用于更新EODDate,BIZDATE");
			step.setRefresh(true);
			config.getStepList().add(step);
			step = new Step();
			step.setType(Type.validateEod);
			step.setValue(value.getText());
			step.setDesc("");
			step.setXpath(changeEodDayClickCurrentDateXpath);
			config.getStepList().add(step);
		}
	}

	/**
	 * 解析setting内容
	 * 
	 * @author chen.pian
	 * @param root
	 * @param config
	 */
	@SuppressWarnings("all")
	public void parseSetting(Element root, CaseConfig config) {
		List<Element> eles = root.selectNodes("setting/property");
		if (eles != null && eles.size() > 0) {
			for (Element ele : eles) {
				config.getSettingMap().put(ele.attributeValue("name"), ele.attributeValue("value"));
			}
		}
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
	}

}
